Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
91.67% covered (success)
91.67%
11 / 12
CRAP
97.33% covered (success)
97.33%
73 / 75
AddVersionListener
0.00% covered (danger)
0.00%
0 / 1
91.67% covered (success)
91.67%
11 / 12
28
97.33% covered (success)
97.33%
73 / 75
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 onFlush
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
13 / 13
 postFlush
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 processVersionableEntities
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
10 / 10
 createVersion
0.00% covered (danger)
0.00%
0 / 1
3.10
77.78% covered (warning)
77.78%
7 / 9
 checkScheduledUpdate
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 checkScheduledCollection
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 checkScheduledDeletion
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 addPendingVersioning
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 computeChangeSet
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 getObjectHash
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 detachVersions
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
<?php
namespace Akeneo\Tool\Bundle\VersioningBundle\EventSubscriber;
use Akeneo\Tool\Bundle\VersioningBundle\Manager\VersionContext;
use Akeneo\Tool\Bundle\VersioningBundle\Manager\VersionManager;
use Akeneo\Tool\Bundle\VersioningBundle\UpdateGuesser\UpdateGuesserInterface;
use Akeneo\Tool\Component\Versioning\Model\Version;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
 * Aims to audit data updates on versionable entities
 *
 * @author    Nicolas Dupont <nicolas@akeneo.com>
 * @copyright 2013 Akeneo SAS (http://www.akeneo.com)
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class AddVersionListener
{
    /** @var object[] */
    protected $versionableEntities = [];
    /** @var string[] */
    protected $versionedEntities = [];
    /** @var string[] */
    protected $versions = [];
    /** @var VersionManager */
    private $versionManager;
    /** @var NormalizerInterface */
    private $versioningNormalizer;
    /** @var UpdateGuesserInterface */
    private $updateGuesser;
    /** @var VersionContext */
    private $versionContext;
    public function __construct(
        VersionManager $versionManager,
        NormalizerInterface $versioningNormalizer,
        UpdateGuesserInterface $updateGuesser,
        VersionContext $versionContext
    ) {
        $this->versionManager = $versionManager;
        $this->versioningNormalizer = $versioningNormalizer;
        $this->updateGuesser = $updateGuesser;
        $this->versionContext = $versionContext;
    }
    /**
     * @param OnFlushEventArgs $args
     */
    public function onFlush(OnFlushEventArgs $args)
    {
        $em = $args->getEntityManager();
        $uow = $em->getUnitOfWork();
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            $this->checkScheduledUpdate($em, $entity);
        }
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            $this->checkScheduledUpdate($em, $entity);
        }
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
            $this->checkScheduledDeletion($em, $entity);
        }
        foreach ($uow->getScheduledCollectionDeletions() as $entity) {
            $this->checkScheduledCollection($em, $entity);
        }
        foreach ($uow->getScheduledCollectionUpdates() as $entity) {
            $this->checkScheduledCollection($em, $entity);
        }
    }
    /**
     * @param PostFlushEventArgs $args
     */
    public function postFlush(PostFlushEventArgs $args)
    {
        $this->processVersionableEntities();
    }
    /**
     * Process the entities to be versioned
     */
    protected function processVersionableEntities()
    {
        foreach ($this->versionableEntities as $versionable) {
            $oid = $this->getObjectHash($versionable);
            $this->createVersion($versionable);
            $this->versionedEntities[] = $oid;
        }
        $versionedCount = count($this->versionableEntities);
        $this->versionableEntities = [];
        if ($versionedCount) {
            $this->versionManager->getObjectManager()->flush();
            $this->detachVersions();
        }
    }
    /**
     * @param object $versionable
     */
    protected function createVersion($versionable)
    {
        $changeset = [];
        if (!$this->versionManager->isRealTimeVersioning()) {
            $changeset = $this->versioningNormalizer
                ->normalize($versionable, 'flat', ['versioning' => true]);
        }
        $versions = $this->versionManager->buildVersion($versionable, $changeset);
        foreach ($versions as $version) {
            $this->versions[] = $version;
            $this->computeChangeSet($version);
        }
    }
    /**
     * Check if an entity must be versioned due to entity changes
     *
     * @param EntityManager $em
     * @param object        $entity
     */
    protected function checkScheduledUpdate($em, $entity)
    {
        $pendings = $this->updateGuesser
            ->guessUpdates($em, $entity, UpdateGuesserInterface::ACTION_UPDATE_ENTITY);
        foreach ($pendings as $pending) {
            $this->addPendingVersioning($pending);
        }
    }
    /**
     * Check if an entity must be versioned due to collection changes
     *
     * @param EntityManager $em
     * @param object        $entity
     */
    protected function checkScheduledCollection($em, $entity)
    {
        $pendings = $this->updateGuesser
            ->guessUpdates($em, $entity, UpdateGuesserInterface::ACTION_UPDATE_COLLECTION);
        foreach ($pendings as $pending) {
            $this->addPendingVersioning($pending);
        }
    }
    /**
     * Check if a related entity must be versioned due to entity deletion
     *
     * @param EntityManager $em
     * @param object        $entity
     */
    protected function checkScheduledDeletion($em, $entity)
    {
        $pendings = $this->updateGuesser
            ->guessUpdates($em, $entity, UpdateGuesserInterface::ACTION_DELETE);
        foreach ($pendings as $pending) {
            $this->addPendingVersioning($pending);
        }
    }
    /**
     * Mark entity as to be versioned
     *
     * @param object $versionable
     */
    protected function addPendingVersioning($versionable)
    {
        $oid = $this->getObjectHash($versionable);
        if (!isset($this->versionableEntities[$oid]) && !in_array($oid, $this->versionedEntities)) {
            $this->versionableEntities[$oid] = $versionable;
        }
    }
    /**
     * Compute version change set
     *
     * @param Version $version
     */
    protected function computeChangeSet(Version $version)
    {
        $om = $this->versionManager->getObjectManager();
        if ($version->getChangeset()) {
            $om->persist($version);
            $om->getUnitOfWork()->computeChangeSet($om->getClassMetadata(ClassUtils::getClass($version)), $version);
        } else {
            $om->remove($version);
        }
    }
    /**
     * Get an object hash, provides different hashes depending on version manager context to allows to log different
     * versions of a same object during a request
     *
     * @param object $object
     *
     * @return string
     */
    protected function getObjectHash($object)
    {
        return sprintf(
            '%s#%s',
            spl_object_hash($object),
            sha1($this->versionContext->getContextInfo())
        );
    }
    /**
     * Clear versions know to this subscribler from the object manager
     */
    protected function detachVersions()
    {
        $om = $this->versionManager->getObjectManager();
        foreach ($this->versions as $version) {
            $om->detach($version);
        }
        $this->versions = [];
        $this->versionableEntities = [];
        $this->versionedEntities = [];
    }
}